home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Plus Leser 15
/
Amiga Plus Leser CD 15.iso
/
Tools
/
Development
/
mmu
/
MuManual
/
Autodocs
/
exceptions.doc
< prev
next >
Wrap
Text File
|
2002-03-12
|
37KB
|
818 lines
E X C E P T I O N - H A N D L E R D E T A I L S
------------------------------------------------------------------------------
Abstract:
This document defines the exception handler mechanism of the mmu.library
and how to make use of it.
Please study it carefully before you try to implement an mmu.lib
exception handler.
For details about the concepts of a "context", read the mmu.doc.
------------------------------------------------------------------------------
What is a bus error exception:
A bus error can be understood as a sort-of "interrupt" which is triggered
by either a software fault or a hardware signal related to acessing memory
or external devices in an unsupported fashion, like accessing a non-available
memory location or writing to a write-protected memory region.
All bus errors are re-directed to the MMU.library, but some of them remain
unhandled and are passed over to the exec.library. Some of the remaining
errors are handled automatically by the library, and those that remain
will cause your "context hooks" to be called, as installed by the
"AddContextHook" and "AddMessageHook" vectors of the mmu.library.
The members of the MC680x0 family generate so called "bus error" for the
following situations:
- True physical bus errors:
The CPU tried to access an external device, but this request was aborted
by pulling the "bus error input" control line (TEA) of the CPU low.
These bus errors are generated by an external bus control logic, avail-
able for some Amiga models, on access of non-available memory.
- Access errors:
These errors are generated by the MMU for a non-valid access of
a memory "page". As far as the MMU is concerned, complete memory is
split into adjacent pages of a fixed size, usually of size 1K or 4K,
and the MMU holds access rules, the so called "properties" for each
of these pages. If these rules are violated, an access error is
generated. The following violations may arise:
The CPU tried to access a page of memory marked by the MMU as
"non-resident", or tried to write to a memory block which is write-
protected, or tried to access a memory block in user mode which is
marked as "supervisor only" access.
Of all of these bus errors, the mmu.library handles only the part which it
is "interested in". True physical bus errors are not handled by the
MMU.library and passed thru to the exec exception handler - which again, calls
the exception handler of the task that caused the exception. Additionally,
the MMU.library does not try to fix bus errors caused by so called "locked
transfers", i.e. the instructions "TAS","CAS" and "CAS2". These instructions
are only useful for multi-processor machines and not supported by the Amiga
hardware architecture anyways. A legal Amiga program *must not* use them.
Furthermore, the "MOVE16" instruction accesses of the 040 and 060 processors
should not be expected to handled fully correctly, too, for the same reason.
The exception handler cannot guarantee that no "double read" situation can
occur; i.e. the handler might then read one location more than once, which
might be undesirable if someone is insane enough to use MOVE16 for a hardware
access.
An additional special case - which might be interesting for the experts -
is writing to memory by "instruction function code". This is only possibly
by loading this function code to the "dfc" register and then using the
privileged "moves" instruction to write instruction data. This is unsupported,
too. Just write instructions with "move", then flush the cache. This is, too,
what the dos.library does when loading code; alternatively, make sure
that this write operation cannot fail. Note that you have to push the
corresponding cache entry anyhow.
All remaining accesses are handled by the MMU library, and are first filtered
again. The MMU library distinguishes:
- User mode and supervisor mode accesses.
All exec tasks MUST run in user mode, and this is the "default" situation.
The user mode exceptions are handled according to the "MMU context" they
are generated by, or, equivalently, the context the current task is attached
to. All supervisor mode accesses are managed by the supervisor context of
the currently active context. Usually, there is only one supervisor context
which is shared amongst all user contexts.
- Data vs. instruction accesses.
Whether the bus error happened due to faulted access of data, or whether
the CPU tried to fetch instructions, and this instruction fetch faulted.
Instruction write accesses, as they might have been generated by a "moves",
might or might not be handled, this is implementation specific. The CPU
itself never generates these accesses when fetching code, the only
possibility is a "moves" instruction.
- Read vs. write accesses.
Whether data was about to be read by the CPU, or the CPU tried to write
out data. Instruction accesses by the CPU are always reads.
Accesses to the stack, as for subroutine calls or "link" instructions
are regarded as data accesses, too. (Read about special caveats about
the stack, below!)
If the CPU fetches an auto vector for an interrupt or any other exception
management, then this access is a "supervisor data space access", and
goes thru the cache. It might not allocate a cache line, though.
Amongst all these types, the MMU library handles certain special cases
automatically. You never see these faults from your context hooks, they
are completely transparent and handled by the library as a special service.
- AbsExecBase reads.
To be able to mark the first memory page as "invalid", the MMU library
filters out accesses to AbsExecBase itself in the first stage of the exception
handler, to emulate them as fast as possible. The library will try to emulate
the most popular instructions in software, for optimal performance. However,
still try to avoid reading AbsExecBase in your code. Make a copy to a static
library base on startup, and then use this backup copy whenever possible.
Most modern C compilers do that automatically anyways, but assembly language
authors should keep this in mind.
DON'T (slow!) DO (fast)
move.l AbsExecBase,a6 move.l mn_SysBase(a4),a6
In case the special threatment of AbsExecBase is not desired, set the
MCXTAG_EXECBASE tag value of the CreateMMUContext() call to FALSE. This might
be interesting for emulators, for example.
Remember that interrupt code is handled by a supervisor context, AND NOT
BY YOUR USER CONTEXT. Hence, accessing AbsExecBase from within interrupts is
always valid, simply because the supervisor context is always build by the
library itself, and this flag is always set on creation of this context.
DO NOT touch the default supervisor context unless you know precisely what
you're doing. Note that all exec interrupt handlers run thru an AbsExecBase
access!
- Legal accesses in the first memory page.
The mmu.library emulates accesses into valid "chip memory" addresses in the
first page in memory. This is again to allow configuring the first page as
"invalid" for tracing access errors in the autovector region. Accesses to
addresses above and including the chip memory start in the first page are
therefore always legal, but slow. These accesses should not happen for
Os 3.0 or better since chip memory starts for newer Os releases always
in the second page. This feature can be disabled again with the
MCXTAG_EXECBASE flag, and can be configured by the MCXTAG_LOWMEMORYLIMIT
tag item.
AVOID THESE ACCESSES, THEY ARE EMULATED IN SOFTWARE AND VERY SLOW.
Starting with Os 3.0, exec tries to help these MMU programs by starting
chip memory at 0x1000, not at 0x0400 as usual, if a 040 or 060 CPU is
available. This means that for page sizes of 4K for the 040 and 060, and
for page sizes of 1K for the 030 and 68851/020 MMU, no accesses need to
be emulated. Please keep this in mind when using the MMU because THE
EMULATION CODE IS SLOW. Try to use these "natural" page sizes whenever
possible.
If not possible, try to relolate the libraries and buffers in this memory
region, usually some graphics.library buffers, the expansion library base,
and - possibly - the exec library. MuMove4K will do this for you.
- Writes to MAPP_ROM pages.
The mmu.library tries to cancel writes to pages marked as "ROM". Writes
to these pages will generate an access error, which is "ignored" by
the mmu.library. Hence, memory regions marked as "ROM" will get an
effective "silent" write protection. However, keep in mind that a write
to "ROM" area is still slow because it includes running the exception handler.
This goes even more for the 060 CPU because it includes copying the "ROM"
to a dummy writable backup page into which the write is then redirected.
A 060 cannot be stopped once it intends to write to memory.
Special care must be taken for "misaligned" writes, i.e. writes that hit
the "border" of a ROM region and go partially into valid RAM, and partially
into "ROM" regions. The MMU.library DOES NOT guarantee that the valid part
of the access is performed! It might abort the complete write!
This shouldn't be much of a problem because writes to ROM should only happen
due to faulty software, and it should only do good to abort these writes
completely.
To say that again: All the cases above are handled by the library trans-
parently, you don't see them from your context hook. However, the following
cases ARE seen from your hook function:
- Access to MAPP_SWAPPED pages.
If a read or write hits a page marked as swapped, the library calls all
hooks in its "swapped" hook list. For details what happens then, see below.
Again, special care must be taken if the access is "misaligned", i.e. hits
more than one page at once. In this case, the library checks if both accesses
are allowed, i.e. go either to valid pages, or to swapped pages. In case
any of the accesses is invalid, the "segmentation fault" hook list, see
below, is called.
- Any other access,
as there are accesses to "MAPP_INVALID" pages or to supervisor only pages
in user mode, or write accesses to write protected pages are passed over
to the "segmentation fault" hook list.
How your hook function is called:
---------------------------------
The mmu.library keeps two priority sorted lists for each MMU context, one
for the "swapped" hook list for pages currently "swapped out", and another
for the "segmentation fault" hook lists. The mmu library first determinates
which list has to be used to handle the access, and then tries to find the
right handler. The following algorithm is used:
- Try the handler of higher priorities first;
- Check whether this hook is activated or not, skip it if it is
deactivated;
- Check whether the hook is "task specific", i.e. whether the
MADTAG_TASK tag was given at the time the hook was added with
AddContextHook().
If the hook is task specific, check whether the faulty task
is identical to the task handled by the hook and skip the
handler if this is not the case.
- As soon as a match has been found, call the routine at the
address provided by the MADTAG_CODE tag. This should be an
assembly routine, mainly for speed reasons. Furthermore,
parameters are passed in registers, similar to the exec.library
interrupts, and return values have to be provided in the d0
register and in the "Z" bit of the processor.
You're called like this:
Register a0 contains a pointer the exception data structure,
discussed below.
Registers a1 and a4 contain the data provided by the MADTAG_DATA
tag on creation.
Register a6 contains the pointer to the mmu.library base.
Register a5 contains the pointer to your code.
You may alter registers d0-d1, a0-a1 and a4-a5. They will be restored by the
library automatically. All other registers MUST BE preserved.
In case you want make use of the exception data structure, please keep in
mind that it is only valid as long as your exception hook executes. That
means that you possibly have to make a copy of it for later use.
Remember, too, that you're called in supervisor mode, with all interrupts
disabled. DO NOT ACCESS ABSEXECBASE IN YOUR CODE, use either a private
copy, or the copy in the ExceptionData structure below. Accessing
AbsExecBase MIGHT BE FATAL!
Additionally, THERE IS NO DIRECT ACCESS to the CPU specific exception
stack frame. Exception hooks are - and that's the great point about the
mmu library - CPU indepentent. All data you need is provided in the
exception data structure, DO NOT MAKE ANY ASSUMPTIONS which CPU you're
running at, or how the stack frame looks like. This is the matter of the
library.
On return, you should set d0 to 0 and set the zero flag to indicate that
your exception handler was able to handle the fault and return to the
library with a plain "RTS". In that case, the library will no longer look
for more handlers.
If you haven't been able to handle the fault, return 1 in d0 and clear
the zero flag (i.e. return a "ne" condition in the CCR register).
In case the library did not find any hook that claimed to be able to
repair the fault, it will redirect control back to the exec exception
handler (which will, by default, run the GURU).
BE WARNED! Another bus error within the bus error handler IS FATAL AND WILL
CAUSE A GURU! Within the exception handler, YOU MUST ENSURE that all memory
accesses are valid or you're lost. The library function GetPagePropertiesA()
is ideal for this purpose: First, it is safe to get called within interrupts
and bus errors, it is rather optimized and you find all information required
to call it in the ExceptionData structure.
The ExceptionData structure
---------------------------
This structure is the key to the powers of the MMU. Your handler is called
with a pointer in *a0 to this structure:
struct ExceptionData {
struct Task *exd_Task;
struct MMUContext *exd_Context;
ULONG *exd_Descriptor;
ULONG *exd_NextDescriptor;
APTR exd_FaultAddress;
APTR exd_NextFaultAddress;
ULONG exd_UserData;
ULONG exd_NextUserData;
ULONG exd_Data;
APTR exd_ReturnPC;
ULONG exd_Flags;
ULONG exd_Properties;
ULONG exd_NextProperties;
UBYTE exd_internal;
UBYTE exd_FunctionCode;
UBYTE exd_Level;
UBYTE exd_NextLevel;
ULONG exd_DataRegs[8];
ULONG exd_AddrRegs[7];
UWORD *exd_SSP;
UWORD *exd_USP;
struct ExecBase *exd_SysBase;
struct MMUBase *exd_MMUBase;
};
Let's discuss the fields in this structure:
exd_Task contains the pointer to the task structure of the task
that caused the exception.
If this hook was added to a supervisor context, this
field is meaningless and must be left alone.
exd_Context contains the ID of the MMU context responsible for the
fault. This is always the context the hook was added to.
exd_Descriptor contains a pointer to the true hardware MMU descriptor which
handles the fault. This is usually of less interest except
for special purposes.
In case of an indirect descriptor, this points NOT to the
final page descriptor, but to the indirect descriptor.
WARNING! In case you read this descriptor or write a new
one, you *MUST* call CacheClearE() to make sure that the
descriptor is really written out. The library will always
flush descriptors correctly. Do *NOT* expect descriptors in
non-cacheable memory.
exd_NextDescriptor In case of a misaligned access, i.e. an access that
spawns two pages because it hit a page boundary, this is the
descriptor for the end address of the access, and
exd_Descriptor is the descriptor for the first address
of the access. If the access is aligned, both pointers are
identically.
NOTE THAT THIS DESCRIPTOR NEED NOT TO POINT TO A HIGHER
ADDRESS. An example for a "backwards" misaligned access is
the "movem.l regs,-(ax)" instruction.
WARNING! In case you read this descriptor or write a new
one, you *MUST* call CacheClearE() to make sure that the
descriptor is really written out. The library will always
flush descriptors correctly.
exd_FaultAddress The address that faulted, the start address of the access.
exd_NextFaultAddress The end address of the access that faulted, inclusive.
For a long word access, this would be exd_FaultAdress + 3,
for a byte access, this would be identically to
exd_FaultAdress.
Note that it may well be that exd_NextFaultAddress is a
SMALLER address than exd_FaultAdress. This might, for example,
happen for a movem with pre-decrement addressing mode, i.e.
a "movem.l d0-d7/a0-a6,-(a7)". Exception handlers must be
aware of this special situation.
Especially, it *might* happen that the library is not
able to report the full range of the access violation, but
only a part of the violation. However, it will always
report enough to ensure that all memory pages that
participate in this fault can get corrected. In the
above situation, the library *might* only report the long
word access hitting the page boundary such that you
get notice that two pages are involved in the operation,
not one.
exd_UserData The user data provided for invalid and swapped pages, as
defined by SetPropertiesA() and SetPagePropertiesA().
This will be NULL in case no user data is available.
exd_NextUserData In case of a misalgined access, the user data of the
second page that was involved in the exception. If the
access was aligned, this is identical to exd_UserData.
exd_Data If the access was a write access and the
EXDF_WRITEDATAUNKNOWN flag (see below) is cleared, then
this long word will contain the data the CPU tried to
write out. The data is right-justified in this field, i.e.
bytes will use bits 7..0, words will use bits 15..0 and
long word accesses will use the complete field.
The size is available by subtracting the exd_FaultAddress and
exd_NextFaultAddress fields.
On movem faults, the complete write data is unfortunately
not available. You may see movem faults as a series of
long word writes on some CPUs, while this instruction is
atomic on other CPUs. There's nothing the MMU library can
do for you here.
This data should be used only for debugging purposes and
"Enforcer like" applications, and for applications where you
can guarantee that movems do not occur, i.e. emulators that
threat "computer hardware" by the MMU and might read the
data written to the emulated hardware right here.
To allow the MMU library to fetch write data, you *MUST*
set the "MAPP_REPAIRABLE" bit of pages you want write data
for. In all other cases, you *might* get write data, but
this is a matter of "good luck". (Or, in other words, I don't
want to document the messy details.)
Please do only set this bit in case you really need the
write data because it means much more work for the library
and much "higher magic" to get it, especially for the 060
CPU.
In case you want to allow the CPU to continue execution and
you somehow managed to write out the data itself (or you
want to ignore the faulty access), set the
EXDF_WRITECOMPLETE bit of the flags field below.
In case you do not set this bit, the CPU will attempt to
re-run the faulty instruction. In case you haven't been able
to repair the fault using some other technique (as for
example by swapping in the faulty page), your exception
handler will be called again.
In case of a read fault, you may place the data to be
read back in this field and set the EXDF_READBACK flag
in the flags value below. The MMU library will then
attempt to place this value in the input pipe of the CPU
and to repair the faulty access by providing this dummy
data. The data has to be placed right-justified in this
field again.
Certain restrictions arise, again:
First, a movem might show up as several exceptions, hence
you might be able to provide data for each register loaded,
or it might show up as one single exception. In this case,
all registers will be loaded with the same value. (It was
hard enough to emulate this, so please don't complain...)
Instruction faults can't be repaired like this. (I would
have been able to provide this exclusively for the 030 and
the 020, but I choose not to do so for orthogonality).
Don't try it, the mmu.library will call the exec exception
handler directly in case you attempt this.
Hence, you cannot feed the instruction pipeline of the
CPU manually.
exd_ReturnPC The program counter of the instruction that caused the
fault. This might be only an approximate value due to the
instruction prefetch feature of all CPUs of the 680x0
family. In case a branch was performed after the faulty
instruction was loaded, and before it was detected, this
PC might be completely useless. However, the program
is guaranteed to continue "at the right place".
In case you set the EXDF_CALL flag, this field should be
set by your handler to the address of a small procedure
which will be called in user mode, in the context of the
task that caused the exception. This routine could, for
example, send a message out to a "daemon" to get the
fault repaired and could run into a WaitPort() to get
an answer from the daemon.
The user routine has access to all registers of the faulty
program, and must therefore preserve all registers unless
it attempts to alter them "on purpose". The exception data
IS NO LONGER AVAILABLE, neither in the mmu library base
nor in any register. If you want to make use of it, make a
copy of it in the exception handler to a private place and
access it in this user routine.
Alternatively, use the "message hook" mechanism of the
library which provides all this for you already if you don't
want to get a headache.
The user routine should then return with a "RTS" to the
library code (note that the stacked PC points to some code
in the library, not to the faulty code itself! It's truely
deep magic to restore all this information) which will then
rerun the faulty instruction.
If you haven't been able to repair the cause of the fault,
the exception hook will be called again!
exd_Flags A combined input/output flags long word. Amongst private
flags you'd better not care about, the following input
flags are available:
EXDF_WRITE a write fault if set. If reset, a read fault.
EXDF_INSTRUCTION a fault on fetching an instruction.
As a special case, this *could* be a write fault
if someone tried to write out instruction data
with an instruction function code using the
"moves" instruction. This is actually unsupported
by the library and should be avoided.
EXDF_WRITEPROTECTED failed due to write protection of the destination.
EXDF_SUPERVISOR failed due to supervisor only protection of the
source/destination.
EXDF_WRITEDATAUNKNOWN the write data was lost, exd_Data is invalid.
Remember that you MUST set the MAPP_REPAIRABLE
property flag for those pages you want write data
for, and you should only set this bit if you really
really want them. It means quite a lot of trouble
for the library to provide the write data, hence
things are not getting faster with it.
EXDF_MISALIGNED The access was misaligned, i.e. more than one
page was involved in the access. Prepare to
swap in more than one page, for example.
The following flags can be set by your code to let the
library know what to do about the fault:
EXDF_READBACK abort a faulty read and provide the exd_Data word
as input for the CPU. Do not try to rerun the access.
EXDF_WRITECOMPLETE abort a faulty write, do not try to rerun it.
EXDF_CALL call routine whose address is in exd_ReturnPC in
user mode in the context of the task that caused
the exception. This routine will be run as "sub-
routine" of the task that faulted, and might be
used to sent out a message and wait for its reply
to get the missing page fixed by another supervisor
task.
THIS FUNCTION IS NOT AVAILABLE for a supervisor
context. The mmu.library will guru in case you try
to.
Especially, the message hook mechanism described
below uses this flag.
Note that the library does not check for you whether
Wait()-ing is useful here, as for example if the task
is in Forbid state. It's the matter of the exception
hook to check this.
The library message hook exception does
this, for example.
exd_Properties The property flags of the page that was responsible for
the fault.
exd_NextProperties In case of a misaligned access, the flags of the second
page involved in the access.
exd_internal Leave this alone.
exd_FunctionCode The function code of the access. The following values
should be expected:
For the user context hooks:
1 User data access
2 User code access
For the a supervisor context and its hooks:
5 Supervisor data access
6 Supervisor code access
All remaining function codes relate to physical bus errors
and should not be passed thru to your context hook.
exd_Level The level of the MMU tree at which the access fault happend
and at which the MMU descriptor resides.
This NEED NOT to be the "page level" for early termination
descriptors or invalid descriptors at a higher level.
Experts should note that this is not even guaranteed for
the 040 or 060!
Furthermore, in case of an indirect descriptor, this is the
level of the pointer pointing to the final page descriptor,
not the level of the real page descriptor.
This is advanced information you usually should not care
about.
Level A of the MMU tree is encoded as "0", level B as "1"
and so on. Note that this numbering is different from the
MC680x0 internal counting.
exd_NextLevel In case of a misaligned access, the level of the second
descriptor involved in the fault.
exd_DataRegs A copy of the data registers at the time the fault happened.
These images are copied back as soon as the exception
terminates, hence a "higher magic" exception handler could
alter these...
exd_AddrRegs A copy of the adress registers, but without the stack
pointer.
exd_SSP The supervisor stack pointer. This points directly to the
processor specific exception stack frame. The first UWORD
of this stack frame is the copy of the status register at
the time of the fault.
BE WARNED! Everything else is processor specific, the
mmu.library might also want to modify this exception stack
frame, so hands off!
exd_USP The user stack pointer.
exd_SysBase A pointer to the library base of the "exec.library". DO NOT
ACCESS ABSEXECBASE within the exception handler. Use either
a private copy, or this pointer.
NOT FOLLOWING THIS RULE MIGHT BE FATAL!
exd_MMUBase A pointer to the library base of the mmu.library, also in
register a6.
Additional notes about the exception handlers
---------------------------------------------
In case you're going to write an automatic stack extension program, you
should keep in mind that the EXDF_CALL mechanism requires about 300 bytes
of user stack space. Similar, task switching takes some user stack, too.
The library message hook is prepared to handle this situation by swapping
in its private stack space.
For the same reason, you might have to add "Switch" and "Launch" handlers
to allow exec to store the user registers safely. Unfortunately, this
is done with supervisor accessed going to the user stack, and not with a
"moves".
Additional wierdos to watch out for
-----------------------------------
There are unfortunately a number of "features" of each CPU which should not
go unmentioned:
The 68020/68851 and 68030:
Execution of instructions in a access-protected "zero page" is really,
really slow. Please keep this in mind!
"moves" to instruction space is unsupported (goes for all other CPUs
as well).
The FPU and exceptions: This is a dark chapter in exception handling.
Of a faulty fmove, only the first two long words are checked by the
68030 and therefore at most two hits get reported, all others go
unnoticed. Of a faulty fmovem, only the first two long words hits get
reported, too. The CPU will ignore all other hits and continue
execution.
This means specifically that a Debugger tool might not be able to see
all hits that happened on these instructions, and you will not be
able to capture them for private use.
VM systems shouldn't have much problems with that, though, since the
invalid page should have been swapped in and each access can hit at
most two pages at once if it is misaligned. However, you must be
prepared that the mmu.library will not be able to set the "misaligned"
flag correctly in this case. Rather, the CPU will be re-run with only
partially repaired pages, and might invoke an exception handler
again.
The 68040:
Movem writeback data is not always available, and certain movems can't
be continued safely. In case a movem read or write goes to an invalid
page other than at level 2 or 3, the mmu library has to abort the movem
completely. There's nothing I can do against it. To avoid this situation
in case you really need the movem data, set the MAPP_REPAIRABLE property.
Movems to a register included in the register list itself might not be
rerun safely in all circumstances. Avoid them.
Readback data is only available for all destination registers at once.
The FPU and exceptions: Even more problems than with the 030.
If a fmove or fmovem moves more than two long words, for example a
fmove.x or any fmovem, the processor will only notice the first
access of a faulty page. If the instruction would catch more than
one hit, the processor restarts the instruction completely from the
beginning. This means for debugging tools that you will *not* be
able to fix a faulty instruction of this kind at all, the processor
will loop forever. Note that even disassembling the instruction at
the PC will not help because the PC might point to an FPU instruction
even if the hit was caused by the instruction before. Hence, the
following two instructions
move.l d0,(a3)
fmovem.x fp0,(a3)
with a3 pointing to an invalid page, will both invoke the exception
handler with the same PC value, namely the address of the fmove!
This might be a problem for VMM systems as well: Because there's no
single flag that tells whether the hit was caused by an FPU instruction
or not, the processor might re-run a FPU instruction as mentioned
above again, possibly accessing the two different pages. If the VMM
system would have swapped in the second page by the first hit, and
removed the first page accessed by this instruction from memory,
re-running this instruction will cause another VMM hit, this time
from the first page. It is therefore important that a VMM system
*MUST NOT* swap out a page previously swapped in. At least two pages
must be available at once.
The 68060:
The library handles branch cache flushes transparently, no need to
worry about them. So much for the good point.
Writeback data is usually not available unless the library emulates
this with some heavy magic and is told to do so with the MAPP_REPAIRABLE
flag. Things are getting even worse if no exception handler wants to
make use of the writeback data and returns with the EXDF_WRITEBACK flag
cleared. In this case, the mmu.library tries to rerun the faulty
instruction again, even though it already executed. It tries to rebuild
all the registers (except for the FPU registers and the supervisor model
and the SSP) and then returns to the PC of the faulty instruction.
Whether this mechanism is reliable or not has to be proven.
Except that, all 040 restrictions regarding movems apply here again, see
above.
In case of a non-locked read-modify-write access, as for example a
addq.l #1,(ax), the library will call the context hooks twice, once
with the read data, and then again with the write data. However, things
are getting rather nasty in case of a misaligned RMW access of which
one part of the access is valid and the other part is not. The 060
manual states that these accesses can't be rerun safely because parts
of the data might have been written back already, and rerunning the
instruction might cause this wrong data to be re-used again. I can't
comment on that feature because I haven't tried to reproduce it yet,
but it may reduce reliability of the 060 for virtual memory appli-
cations.
------------------------------------------------------------------------------
About the page access exception:
This is not a true exception, but is installed and handled the like. It is
setup with AddContextHook(MADTAG_TYPE,MMUEH_PAGEACCESS,....) as all other
exception handlers. The page access hook "hooks in" between the high level
functions SetProperties/GetProperties/RebuildTree and the low level functions
SetPageProperties/GetPageProperties at the other side. It provides a method
to modify the MMU tree on the low level transparently to the high level
functions.
The page access handlers are called whenever RebuildTree() tries to re-
install a page descriptor with the MAPP_SINGLE property bit set. If so, your
handler is allowed to modify this page descriptor. However, you should
remember that the high-level functions are not aware of this modification,
so this feature should be used carefully.
A possible application would be a "Guardian Angel" type program: The idea
is here to mark "free" memory as MAPP_INVALID to be able to detect illegal
memory accesses. Since an AllocMem() has to mark the memory valid it
allocated, the MMU tables have to be adjusted for each allocation. Similary,
released memory has to be made unavailable on a FreeMem(). On the
other hand, AllocMem() is not allowed to break a Forbid() and is hence not
able to call the high-level functions - which might do so. It *has* to use
the low level SetPageProperties(). Since the low level functions do not
inform the higher level about the modification, a program adjusting the
MMU descriptor with the high-level functions will un-do the modifications
made by AllocMem() before. The solution here is to install a page access
handler that checks whether the higher level is going to re-install one
of the modified page descriptors, to intercept here and to set the
MAPP_INVALID flag correctly, by testing whether the addressed memory page is
"free" or not.
Your handler is called with the page access data structure - see below - in
a0, with the provided user data from MADTAD_DATA in a1 and a4 and the
library pointer in a6. Registers d0-d1/a0-a1/a4-a5 are scratch registers,
a6 IS NOT. In case your handler returns with the Z flag set, the library
aborts calling other handlers of lower priority.
Remember that you're called in supervisor mode with all interrupts dis-
abled, as for all other exception handlers. Your code should better be
fast!
Now for the structure:
struct PageAccessData {
ULONG pgad_Level;
void *pgad_Address;
ULONG *pgad_Destination;
struct MMUContext *pgad_Context;
struct MMUBase *pgad_MMUBase;
ULONG pgad_Properties;
ULONG pgad_UserData;
};
pgad_Level: The level at which the MMU descriptor has to be build
for. This is zero for level A, one for level B and so
on. Note that this numbering is different from the
internal MC68K scheme.
Since this handler is only called for MAPP_SINGLE
descriptors, this will always be the page level of the
MMU tree. However, what the page level *is* is up to
the depth of the MMU tree and may vary.
pgad_Address: The logical address the descriptor to be build will
handle. Hence, this gives the address that is to be
translated or mapped.
pgad_Destination: The address the true hardware descriptor will be written
to. There's little reason to make use of this entry.
pgad_Context: The context the MMU tree being rebuild belongs to. This
is the context you installed the page access handler in.
pgad_MMUBase: The library base address.
pgad_Properties: The page properties, defined in mmu/context.h. This
defines which kind of hardware descriptor the library
will build for you.
pgad_UserData: This field will be used for MAPP_SWAPPED, MAPP_INVALID,
MAPP_REMAPPED, MAPP_BUNDLED and MAPP_INDIRECT
descriptors. It will contain user specific information
like a hard disk position for MAPP_SWAPPED or
MAPP_INVALID pages, the destination address for
MAPP_REMAPPED and MAPP_BUNDLED pages, and the destination
descriptor for MAPP_INDIRECT. It is not available if the
MAPP_REPAIRABLE bit is set. In case you modify the
properties, you possibly need to correct this field, too.